#ifndef MODEL_PACKAGE_HPP
#define MODEL_PACKAGE_HPP

#include "Model/CaseEntity.hpp"
#include <string>
#include <list>

namespace Model
{

class Project;
class Requirement;

	// TODO: Update documentation
class MODEL_API Package: public CaseEntity
{
	typedef std::list< Package*> PackageCollection;
	typedef std::list< Requirement*> RequirementCollection;
public:
	typedef PackageCollection::const_iterator ConstPackageIterator;
	typedef PackageCollection::iterator PackageIterator;

	typedef RequirementCollection::const_iterator ConstRequirementIterator;
	typedef RequirementCollection::iterator RequirementIterator;

	friend class Project;

public:
	Package( const NameString& name, const NameString& description = NameString());
	virtual ~Package();

	const NameString& name() const { return name_; }

	/// @brief It sets the name of the package.
	/// @exception DuplicateNameError thrown if the new name is already in use by a sibling package.
	void setName( const NameString& newName);

	/// @brief It gets the description of the package.
	const RichText& description() const { return description_; }

	/// @brief It sets the description of the package. 
	/// @param newDescription the new description of the package, in HTML format.
	void setDescription( const RichText& newDescription);

	// Access to child packages.
	ConstPackageIterator childPackagesBegin() const { return childPackages_.begin(); }
	PackageIterator childPackagesBegin() { return childPackages_.begin(); }
	ConstPackageIterator childPackagesEnd() const { return childPackages_.end(); }
	PackageIterator childPackagesEnd() { return childPackages_.end(); }


	/// @brief It gets the parent package.
	Package* parentPackage() const { return pParent_; }

	/// @brief It checks whether the specified name is used by a child package.
	bool childUsesName( const NameString& name) const;

	/// @brief It checks whether the specified package is a parent of this package.
	bool hasAncestor( Package* pPackage) const;

	/// @brief It returns the global identifier for the package.
	///
	/// The full ID is made by composing the names of the parent packages, starting with the root 
	///	level and ending with this package.
	NameString fullID() const;

	/// @brief It creates a new package and adds it as a child package.
	/// @param packageName the name of the package to be created and added as a child.
	/// @exception DuplicateNameError thrown if the name is already in use by a child package.
	Package* addNewPackage( const NameString& packageName);

	/// @brief It adds the specified package and as a child package.
	///
	/// The package to be added will be moved to this package, so the parent will change.
	///
	/// @param packageName the name of the package to be created and added as a child.
	/// @exception DuplicateNameError thrown if the name is already in use by a child package.
	/// @exception InvalidHierarchyOperationError thrown if the specified package is a parent of 
	///		this package.
	void addNewPackage( Package* package);

	/// @brief It removes the specified package from the list of child packages. 
	/// 
	/// It assumes that the specified package is in the child package list.
	/// 
	/// @param package the package to be removed.
	void removeChildPackage( Package* package);

	/// @brief It creates and adds a new root requirement to the package.
	/// @param name the name of the requirement.
	/// @param description the statement of the root requirement.
	/// @return the created requirement.
	/// @throw DuplicateNameError if the name of the requirement is already used by another root requirement in this 
	///		package.
	Requirement* addRequirement( const NameString& name, const RichText& description = RichText());

	/// @brief It adds the specified requirement to the package.
	/// @param pRequirement the requirement to be added.
	/// @pre the specified requirement is a root requirement.
	void addRequirement( Requirement* pRequirement);

	/// @brief It checks whether the specified name is used by any of the root requirements in this package.
	bool requirementUsesName( const NameString& nameToCheck) const;

	/// @brief It removes the specified requirement from the package.
	/// @param pRootRequirement the requirement to be removed.
	/// @pre the specified requirement is not null.
	/// @pre the specified requirement is a root requirement.
	void removeRequirement( Requirement* pRootRequirement);

	/// @brief It gets the iterator to the beginning of the managed requirements collection.
	/// @{
	RequirementIterator requirementsBegin();
	ConstRequirementIterator requirementsBegin() const;
	/// @}
	/// @brief It gets the iterator to the end of the managed requirements collection.
	/// @{
	RequirementIterator requirementsEnd();
	ConstRequirementIterator requirementsEnd() const;
	/// @}
	/// @brief It gets the number of requirements this package manages.
	std::size_t numberOfRequirements() const;

	/// @brief It sets the project of this package and all the children packages recursively.
	void setProject( Project* pProject);

	/// @brief It gets the project this package belongs to.
	/// @{
	Project* project() { return pProject_; }
	const Project* project() const { return pProject_; }
	/// @}

private:
	NameString name_;
	RichText description_;
	Package* pParent_;
	Project* pProject_;
	PackageCollection childPackages_;
	/// @brief It contains the child packages that were removed.
	PackageCollection removedPackages_;

	RequirementCollection requirements_;
	RequirementCollection removedRequirements_;

private:
	/// @internal
	/// @brief It checks whether the specified name is in use by one of the child packages.
	/// @throw DuplicateNameError in case the name is already used.
	void checkForDuplicateName( const NameString& name);

	/// @internal
	/// @brief It checks whether the specified package can become a child of this package.
	/// @throw InvalidHierarchyOperationError if the package can't be child of this package (e.g. it is the ancestor of 
	///		this package).
	void checkValidMovement( Package* childPackage);
	
	/// @internal
	/// @brief It adds the specified package.
	/// @param removedPackage the iterator pointing to the package to be added from the removed package collection.
	///
	/// This method is called when the specified package was previously removed and it should be added back.
	void addRemovedChildPackage( PackageIterator removedPackage);

	/// @internal
	/// @brief It adds the specified root requirement.
	/// @param pRequirement the requirement that needs to be added.
	/// @pre the specified requirement is a root requirement.
	/// @pre the specified requirement exists in the list of removed root requirements.
	/// @pre the specified requirement doesn't exist in the list of existing root requirements.
	/// @pre the specified requirement is not attached to any package.
	void addRemovedRootRequirement( Requirement* pRequirement);

	/// @internal
	/// @brief It adds the specified root requirement with no checks.
	/// @param pRequirement the requirement to be added.
	/// @pre the specified requirement is a root requirement.
	/// @pre the specified requirement exists in the list of removed root requirements.
	/// @pre the specified requirement doesn't exist in the list of existing root requirements.
	/// @pre the specified requirement is not attached to any package.
	/// @pre the specified requirement doesn't use a duplicate name.
	void safeInsertRootRequirement( Requirement* pRequirement);

	/// @internal
	/// @brief It checks whether the specified requirement is in the list of removed requirements.
	/// @param pRequirement the root requirement to be checked.
	bool isRemovedRootRequirement( Requirement* pRequirement) const;
};

} // namespace Model

#endif //MODEL_PACKAGE_HPP
